/**
 * Copyright (C) 2010 Cloudfarming <info@cloudfarming.nl>
 *
 * Licensed under the Eclipse Public License - v 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.cloudfarming.client.geoviewer.jxmap.map;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.InputEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import nl.cloudfarming.client.geoviewer.LayerNode;
import nl.cloudfarming.client.geoviewer.LayerObject;
import nl.cloudfarming.client.geoviewer.MapController;
import nl.cloudfarming.client.geoviewer.ObjectLayer;
import nl.cloudfarming.client.model.Bean;
import nl.cloudfarming.client.model.BeanNotFoundException;
import org.jdesktop.swingx.JXMapViewer;
import org.netbeans.api.visual.action.ActionFactory;
import org.netbeans.api.visual.action.PopupMenuProvider;
import org.netbeans.api.visual.action.WidgetAction;
import org.netbeans.api.visual.action.WidgetAction.State;
import org.netbeans.api.visual.action.WidgetAction.WidgetDropTargetDragEvent;
import org.netbeans.api.visual.action.WidgetAction.WidgetDropTargetDropEvent;
import org.netbeans.api.visual.action.WidgetAction.WidgetDropTargetEvent;
import org.netbeans.api.visual.action.WidgetAction.WidgetFocusEvent;
import org.netbeans.api.visual.action.WidgetAction.WidgetKeyEvent;
import org.netbeans.api.visual.action.WidgetAction.WidgetMouseEvent;
import org.netbeans.api.visual.action.WidgetAction.WidgetMouseWheelEvent;
import org.netbeans.api.visual.model.ObjectScene;
import org.netbeans.api.visual.model.ObjectSceneEvent;
import org.netbeans.api.visual.model.ObjectSceneEventType;
import org.netbeans.api.visual.model.ObjectSceneListener;
import org.netbeans.api.visual.model.ObjectState;
import org.netbeans.api.visual.widget.LayerWidget;
import org.netbeans.api.visual.widget.Widget;
import org.openide.explorer.ExplorerManager;
import org.openide.nodes.Node;
import org.openide.util.Exceptions;
import org.openide.util.Utilities;

/**
 * Layer panel containint objects
 * object layers will be painted as images with a backgroundPainter by default
 * when the layer is activated, the actual objects (widgets) will be painted over the background.
 *
 * @author Timon Veenstra
 */
public class ObjectLayerPanel extends LayerPanel implements ObjectSceneListener {

    private static final Logger LOGGER = Logger.getAnonymousLogger();
    final ObjectScene scene = new ObjectScene();
    private final JComponent view = scene.createView();
    private boolean incomingSelectionChange = false;
    private boolean outgoingSelectionChange = false;
    //
    // widget layer
    private final LayerWidget mainLayer = new LayerWidget(scene);

    public ObjectLayerPanel(final MapController controller, LayerNode node, JXMapViewer mapViewer, int id) {
        super(controller, node.getLayer(), mapViewer, id, RootMapPanel.LayerGroup.LAYER_ACTIVE);
        assert (node.getLayer() instanceof ObjectLayer);
        ObjectLayer objectLayer = (ObjectLayer) node.getLayer();


        setLayout(new BorderLayout());
        this.scene.setOpaque(false);
        this.scene.addChild(this.mainLayer);
        this.scene.setMaximumBounds(new Rectangle(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE));
        this.scene.addObjectSceneListener(this, ObjectSceneEventType.OBJECT_STATE_CHANGED, ObjectSceneEventType.OBJECT_SELECTION_CHANGED);

        objectLayer.addPropertyChangeListener(ObjectLayer.PROPERTY_OBJECTS, new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                List<LayerObject> oldObjects = (List<LayerObject>) evt.getOldValue();
                List<LayerObject> newObjects = (List<LayerObject>) evt.getNewValue();
                //
                // remove objects no longer present in new list
                //
                List<LayerObject> removedObjects = new ArrayList<LayerObject>(oldObjects);
                removedObjects.removeAll(newObjects);
                removeObjects(removedObjects);
                //
                // add new objects
                //
                List<LayerObject> addedObjects = new ArrayList<LayerObject>(newObjects);
                addedObjects.removeAll(oldObjects);
                //FIXME [TV 14072011] should be a node listener on the maneging explorer manager
//                addObjects(addedObjects);
            }
        });

        if (node.getChildren() == null || node.getChildren().getNodesCount() == 0) {
            LOGGER.warning("Empty ObjectLayer: The LayerNode did not contain any children.");
        } else {
            addObjects(node.getChildren().getNodes());
        }

        add(view);
        setActive(true);


    }

    public static boolean isRightMouseButton(WidgetMouseEvent anEvent) {
        return ((anEvent.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK);
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if (!outgoingSelectionChange && ExplorerManager.PROP_SELECTED_NODES.equals(evt.getPropertyName())) {
            Node[] newSelection = (Node[]) evt.getNewValue();
            incomingSelectionChange = true;
            scene.setSelectedObjects(newSelection.length == 0 ? new HashSet<Node>() : new HashSet<Node>(Arrays.asList(newSelection)));
            incomingSelectionChange = false;
            view.repaint();

        }
    }

    /**
     * remove objects
     * if remove is called from the event dispatch thread it ill be
     * executed immediate, if not it will be scheduled on the dispatch thread.
     *
     * @param objects
     */
    private void removeObjects(final List<LayerObject> objects) {
        if (EventQueue.isDispatchThread()) {
            internalRemoveObjects(objects);
        } else {
            EventQueue.invokeLater(new Runnable() {

                @Override
                public void run() {
                    internalRemoveObjects(objects);
                }
            });
        }
    }

    private void internalRemoveObjects(List<LayerObject> objects) {
        assert objects != null;
        for (LayerObject layerObject : objects) {
            List<Widget> widgets = this.scene.findWidgets(layerObject);
            if (widgets != null) {
                for (Widget w : widgets) {
                    mainLayer.removeChild(w);
                }
                this.scene.removeObject(layerObject);
            }

        }
        this.scene.validate();
    }

    /**
     * add objects
     * if called from the event dispatch thread it ill be
     * executed immediate, if not it will be scheduled on the dispatch thread.
     *
     * @param objects
     */
    private void addObjects(final Node[] nodes) {
        if (SwingUtilities.isEventDispatchThread()) {
            internalAddObjects(nodes);
        } else {
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    internalAddObjects(nodes);
                }
            });
        }
    }

    /**
     * Direct add
     *
     * @param nodes
     */
    private void internalAddObjects(final Node[] nodes) {
        assert SwingUtilities.isEventDispatchThread();

        for (Node node : nodes) {
            addObject(node);
        }
        scene.validate();
    }

    /**
     * Add an object to the panel.
     * this method will add the object to the scene and create a widget attached to it.
     *
     *
     * @param objectNode
     */
    private void addObject(final Node node) {
        assert SwingUtilities.isEventDispatchThread();
        assert !this.scene.getObjects().contains(node);
        LayerObjectWidget widget = new LayerObjectWidget(this.scene, node, getLayer().getPalette(), getMapViewer());


        widget.getActions().addAction(scene.createObjectHoverAction());
        widget.getActions().addAction(scene.createSelectAction());

        //FIXME [TV] this is how it should work, but popup is never display :$
//        widget.getActions().addAction(ActionFactory.createPopupMenuAction(new PopupMenuProvider() {
//
//            @Override
//            public JPopupMenu getPopupMenu(Widget widget, Point localLocation) {
//                    JPopupMenu menu = Utilities.actionsToPopup(node.getActions(true), node.getLookup());
//                    return menu;
//            }
//        }));
//
        //POPUP menu workaround
        widget.getActions().addAction(new WidgetAction() {

            @Override
            public State mouseClicked(Widget widget, WidgetMouseEvent event) {
                return State.REJECTED;
            }

            @Override
            public State mousePressed(Widget widget, WidgetMouseEvent event) {
                return State.REJECTED;
            }

            @Override
            public State mouseReleased(Widget widget, WidgetMouseEvent event) {
                System.err.println("mouseReleased " + event.isPopupTrigger());
                if (isRightMouseButton(event)) {
                    JPopupMenu menu = Utilities.actionsToPopup(node.getActions(true), node.getLookup());
                    menu.show(view, widget.getLocation().x, widget.getLocation().y);
                    return State.CONSUMED;
                }
                return State.REJECTED;
            }

            @Override
            public State mouseEntered(Widget widget, WidgetMouseEvent event) {
                return State.REJECTED;
            }

            @Override
            public State mouseExited(Widget widget, WidgetMouseEvent event) {
                return State.REJECTED;
            }

            @Override
            public State mouseDragged(Widget widget, WidgetMouseEvent event) {
                return State.REJECTED;
            }

            @Override
            public State mouseMoved(Widget widget, WidgetMouseEvent event) {
                return State.REJECTED;
            }

            @Override
            public State mouseWheelMoved(Widget widget, WidgetMouseWheelEvent event) {
                return State.REJECTED;
            }

            @Override
            public State keyTyped(Widget widget, WidgetKeyEvent event) {
                return State.REJECTED;
            }

            @Override
            public State keyPressed(Widget widget, WidgetKeyEvent event) {
                return State.REJECTED;
            }

            @Override
            public State keyReleased(Widget widget, WidgetKeyEvent event) {
                return State.REJECTED;
            }

            @Override
            public State focusGained(Widget widget, WidgetFocusEvent event) {
                return State.REJECTED;
            }

            @Override
            public State focusLost(Widget widget, WidgetFocusEvent event) {
                return State.REJECTED;
            }

            @Override
            public State dragEnter(Widget widget, WidgetDropTargetDragEvent event) {
                return State.REJECTED;
            }

            @Override
            public State dragOver(Widget widget, WidgetDropTargetDragEvent event) {
                return State.REJECTED;
            }

            @Override
            public State dropActionChanged(Widget widget, WidgetDropTargetDragEvent event) {
                return State.REJECTED;
            }

            @Override
            public State dragExit(Widget widget, WidgetDropTargetEvent event) {
                return State.REJECTED;
            }

            @Override
            public State drop(Widget widget, WidgetDropTargetDropEvent event) {
                return State.REJECTED;
            }
        });



//        widget.getActions().addAction(ActionFactory.createPopupMenuAction(new PopupMenuProvider() {
//
//            @Override
//            public JPopupMenu getPopupMenu(Widget widget, Point localLocation) {
//                JPopupMenu m = Utilities.actionsToPopup(node.getActions(true), node.getLookup());
//                m.add(new JMenuItem("la die da"));
//                m.setVisible(true);
//                return m;
//            }
//        }));  


        this.mainLayer.addChild(widget);
        this.scene.addObject(node, widget);

    }

    /**
     * activate or deactivate this layer
     * when layer gets activated, the view will be made visible, painting all containing objects as widgets
     * and providing interaction.
     *
     * @param active
     */
    void setActive(boolean active) {
        this.view.setVisible(active);
        setBackgroundPainter(active ? null : super.getPainter());
        this.view.repaint();
    }

    @Override
    public Component getComponent() {
        return view;
    }

    @Override
    public void objectAdded(ObjectSceneEvent event, Object addedObject) {
    }

    @Override
    public void objectRemoved(ObjectSceneEvent event, Object removedObject) {
    }

    @Override
    public void objectStateChanged(ObjectSceneEvent event, Object changedObject, ObjectState previousState, ObjectState newState) {
        if (!incomingSelectionChange && changedObject instanceof Node) {
            Node node = (Node) changedObject;
            outgoingSelectionChange = true;
            //
            // add node to the selection
            //
            if (!previousState.isSelected() && newState.isSelected()) {
                try {
                    getLayer().getDataProvider().addBeanToSelection(node.getLookup().lookup(Bean.class));
                } catch (BeanNotFoundException ex) {
                    Exceptions.printStackTrace(ex);
                }
            }
            //
            // remove node from the selection
            //
            if (previousState.isSelected() && !newState.isSelected()) {
                try {
                    getLayer().getDataProvider().removeBeanFromSelection(node.getLookup().lookup(Bean.class));
                } catch (BeanNotFoundException ex) {
                    Exceptions.printStackTrace(ex);
                }
            }
            outgoingSelectionChange = false;
        }
    }

    @Override
    public void selectionChanged(ObjectSceneEvent event, Set<Object> previousSelection, Set<Object> newSelection) {
        for (Object o : previousSelection) {
            Widget widget = event.getObjectScene().findWidget(o);
            widget.setForeground(getLayer().getPalette().getColorForState(ObjectState.createNormal().deriveSelected(false)));
        }
        for (Object o : newSelection) {
            Widget widget = event.getObjectScene().findWidget(o);
            widget.setForeground(getLayer().getPalette().getColorForState(ObjectState.createNormal().deriveSelected(true)));
        }
    }

    @Override
    public void highlightingChanged(ObjectSceneEvent event, Set<Object> previousHighlighting, Set<Object> newHighlighting) {
    }

    @Override
    public void hoverChanged(ObjectSceneEvent event, Object previousHoveredObject, Object newHoveredObject) {
    }

    @Override
    public void focusChanged(ObjectSceneEvent event, Object previousFocusedObject, Object newFocusedObject) {
    }
}
